package com.iwhalecloud.citybrain.flow.platform.manager.application.service.impl.object;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.JsonNode;
import com.github.pagehelper.PageInfo;
import com.iwhalecloud.citybrain.flow.platform.manager.application.exceptions.ProcessObjectMetaQueryException;
import com.iwhalecloud.citybrain.flow.platform.manager.application.service.FlowObjectMetaService;
import com.iwhalecloud.citybrain.flow.platform.manager.application.utils.JsonUtils;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.object.ProcessObjectMetaAttributeDTO;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.object.ProcessObjectMetaDTO;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.object.ProcessObjectMetaQueryDTO;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.object.ProcessObjectMetaQueryResultDTO;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.object.ProcessObjectMetaQueryVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.model.object.ProcessObjectMetaVo;
import com.iwhalecloud.citybrain.flow.platform.manager.domain.repository.FlowObjectRepository;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.Model;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author lich
 * @date 2021/11/15
 */
@Service
public class FlowObjectMetaServiceImpl implements FlowObjectMetaService {

    @Autowired
    private FlowObjectRepository flowObjectRepository;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private HistoryService historyService;

    @Override
    public ProcessObjectMetaQueryResultDTO queryObjectMeta(ProcessObjectMetaQueryDTO queryDTO) {
        ProcessObjectMetaQueryVo queryVo = convertQueryDTO(queryDTO);
        PageInfo<ProcessObjectMetaVo> pageInfo = flowObjectRepository.queryProcessObjectMeta(queryVo);
        // 如果没查询到数据,则再次查询最新版本流程模型配置
        if(Objects.isNull(pageInfo) || CollectionUtils.isEmpty(pageInfo.getList())) {
            Model model = repositoryService.createModelQuery()
                    .modelKey(queryVo.getFlowCode()).latestVersion().singleResult();
            queryVo.setDeployId(model.getDeploymentId());
            pageInfo = flowObjectRepository.queryProcessObjectMeta(queryVo);
        }
        ProcessObjectMetaQueryResultDTO resultDTO = new ProcessObjectMetaQueryResultDTO();
        resultDTO.setPageNum(pageInfo.getPageNum());
        resultDTO.setPageSize(pageInfo.getPageSize());
        resultDTO.setTotal(pageInfo.getTotal());

        resultDTO.setObjectMetaDTOList(pageInfo.getList().stream().map(
            vo -> convertObjectMetaVo2DTO(vo, queryDTO.getWithBlob())
        ).collect(Collectors.toList()));

        return resultDTO;
    }

    private ProcessObjectMetaDTO convertObjectMetaVo2DTO(ProcessObjectMetaVo metaVo, Boolean withJson) {
        ProcessObjectMetaDTO metaDTO = new ProcessObjectMetaDTO();
        metaDTO.setCode(metaVo.getObjectMetaCode());
        metaDTO.setVersion(metaVo.getObjectMetaVersion());
        metaDTO.setFlowCode(metaVo.getFlowCode());
        metaDTO.setNodeCode(metaVo.getNodeCode());
        metaDTO.setDeployId(metaVo.getDeployId());

        if (withJson == null || withJson == false) {
            return metaDTO;
        }
        JsonNode objectMetaNode = JsonUtils.readTree(metaVo.getObjectMetaJson());
        if (objectMetaNode == null) {
            return metaDTO;
        }
        parseAndPopulateLayoutInfo(objectMetaNode, metaDTO);
        parseAndPopulateAttributeInfo(objectMetaNode, metaDTO);

        return metaDTO;
    }

    private void parseAndPopulateLayoutInfo(JsonNode objectMetaNode, ProcessObjectMetaDTO dto) {
        JsonNode layout = objectMetaNode.get("layout");
        String layoutState = getByKeyFromJsonNodeAsStr(layout, "state");
        String layoutUrl = getByKeyFromJsonNodeAsStr(layout, "url");
        String layoutName = getByKeyFromJsonNodeAsStr(layout, "name");
        dto.setLayoutState(layoutState);
        dto.setLayoutUrl(layoutUrl);
        dto.setLayoutName(layoutName);
    }

    private void parseAndPopulateAttributeInfo(JsonNode objectMetaNode, ProcessObjectMetaDTO dto) {
        JsonNode attributes = objectMetaNode.get("attributes");
        if (attributes == null) {
            return;
        }

        List<ProcessObjectMetaAttributeDTO> attributeDTOList = new ArrayList<>(attributes.size());
        for (int i = 0; i < attributes.size(); i++) {
            JsonNode element = attributes.get(i);
            ProcessObjectMetaAttributeDTO attributeDTO = new ProcessObjectMetaAttributeDTO();
            attributeDTO.setCode(getByKeyFromJsonNodeAsStr(element, "code"));
            attributeDTO.setName(getByKeyFromJsonNodeAsStr(element, "name"));
            attributeDTO.setType(getByKeyFromJsonNodeAsStr(element, "type"));
            attributeDTO.setDescription(getByKeyFromJsonNodeAsStr(element, "description"));

            attributeDTO.setEditable(getByKeyFromJsonNodeAsBoolean(element, "editable"));
            attributeDTO.setRequired(getByKeyFromJsonNodeAsBoolean(element, "required"));
            attributeDTO.setVisible(getByKeyFromJsonNodeAsBoolean(element, "visible"));
            attributeDTOList.add(attributeDTO);
        }

        dto.setAttributes(attributeDTOList);
    }

    private static String getByKeyFromJsonNodeAsStr(JsonNode jsonNode, String key) {
        JsonNode n = jsonNode.get(key);
        if (n == null) {
            return null;
        }
        return n.asText();
    }

    private static Boolean getByKeyFromJsonNodeAsBoolean(JsonNode jsonNode, String key) {
        JsonNode n = jsonNode.get(key);
        if (n == null) {
            return null;
        }
        return n.asBoolean();
    }



    private ProcessObjectMetaQueryVo convertQueryDTO(ProcessObjectMetaQueryDTO queryDTO) {
        if (StringUtils.isNotEmpty(queryDTO.getProcessInstanceId()) && StringUtils.isNotEmpty(queryDTO.getFlowCode())) {
            throw new ProcessObjectMetaQueryException("流程实例id和流程编码仅能提供其中一个，要么按照流程实例查询，要么按照流程编码查询");
        }

        ProcessObjectMetaQueryVo queryVo = new ProcessObjectMetaQueryVo();
        if (StringUtils.isNotEmpty(queryDTO.getProcessInstanceId())) {
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .processInstanceId(queryDTO.getProcessInstanceId()).singleResult();
            if (Objects.isNull(processInstance)) {
                // 再查询下历史流程记录
                HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(queryDTO.getProcessInstanceId()).singleResult();
                // 当前正在运行的和已经走完的流程流程实例都不存在,那就不存在,直接抛出异常
                if (Objects.isNull(historicProcessInstance)) {
                    throw new ProcessObjectMetaQueryException(String.format("流程实例 %s 不存在", queryDTO.getProcessInstanceId()));
                }
                // 兼容历史的
                queryDTO.setFlowCode(historicProcessInstance.getProcessDefinitionKey());
                queryVo.setDeployId(historicProcessInstance.getDeploymentId());
            } else {
                queryDTO.setFlowCode(processInstance.getProcessDefinitionKey());
                queryVo.setDeployId(processInstance.getDeploymentId());
            }
        } else if (StringUtils.isNotEmpty(queryDTO.getFlowCode())) {
            Model keyModel = repositoryService.createModelQuery()
                .modelKey(queryDTO.getFlowCode()).latestVersion().singleResult();
            if (keyModel == null) {
                throw new ProcessObjectMetaQueryException(String.format("流程 %s 不存在", queryDTO.getFlowCode()));
            }
            queryVo.setDeployId(keyModel.getDeploymentId());
        } else if (StringUtils.isEmpty(queryDTO.getObjectMetaCode())){
            throw new ProcessObjectMetaQueryException("流程实例id，流程模板编码和对象类型编码，三者至少传一个");
        }

        BeanUtils.copyProperties(queryDTO, queryVo);
        queryVo.setWithJson(queryDTO.getWithBlob());

        return queryVo;
    }
}
